package com.xpec.c4.h5net;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map.Entry;
import java.util.TreeMap;

import net.sf.cindy.Session;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.json.JSONException;
import org.json.JSONObject;

import com.ms.game.slot.base.message.SlotGameDoSeatVerifyRequest;
import com.ms.service.error.ErrorCode;
import com.ms.service.module.baccarat.message.SBacrDoSeatResponse;
import com.ms.service.module.zone.message.CPlayerLoginZoneRequest;
import com.ms.service.module.zone.message.CPlayerSwitchZoneRequest;
import com.ms.service.module.zone.message.SPlayerLoginZoneResult;
import com.ms.service.module.zone.message.SPlayerSwitchZoneResponse;
import com.xpec.c4.codec.CodecUtility;
import com.xpec.c4.game.time.TimeSys;
import com.xpec.c4.game.utility.GUID;
import com.xpec.c4.message.MessageTypes;
import com.xpec.c4.net.XSENetConstant;
import com.xpec.c4.net.XSENetGlobal;
import com.xpec.c4.net.handler.ConsolidatingAutoFlusher;
import com.xpec.c4.service.message.NetMessage;
import com.xpec.c4.service.module.ModuleName;
import com.xpec.c4.service.module.ServerProperties;
import com.xpec.c4.service.module.common.PlayerCommonInfoManager;

public class H5XSENetSession
{
	private static final Log log = LogFactory.getLog(H5XSENetSession.class);

	private static final int DELAYTIME = 90;
	
	//cindy
	private Session mSession = null;
	
	//netty
	private Channel mChannel = null;
	private ConsolidatingAutoFlusher mAutoFlusher = null;
	
	private HashMap<String, Object> parameters = new HashMap<String, Object>();
	
	// tcp packet size
	private int tcpPacketSize = 4096;
	
	/////////////////////////////////////////////////////////////////////////
	
	public H5XSENetSession(Session session)
	{
		mSession = session;
	}
	
	public H5XSENetSession(Channel channel)
	{
		mChannel = channel;
		
		mAutoFlusher = (ConsolidatingAutoFlusher)mChannel.getPipeline().get("ConsolidatingAutoFlusher");
	}
	
	public void send(Object gameMessage, int priority)
	{
		//使用編譯最佳化
		if( XSENetGlobal.mLibType == XSENetConstant.LIB_CINDYMODE )
		{
			if( !mSession.isStarted() )
				return;
			
			synchronized(mSession)
			{
				mSession.send(gameMessage, priority);
			}
		}
		else if( XSENetGlobal.mLibType == XSENetConstant.LIB_NETTYMODE )
		{
			if( !mChannel.isConnected() )
				return;
			
			NetMessage message = (NetMessage) gameMessage;

			int messageType = message.getMessageType();
			byte[] bb = message.getMessageContentBytes();
			Object messageContent = CodecUtility.decodeByte(Arrays.copyOfRange(
					bb, 1,bb.length));
			CPlayerSwitchZoneRequest request = new CPlayerSwitchZoneRequest();
			GUID sessionId = message.getSessionID();
			switch (messageType) {
			case MessageTypes.S_ZONE_SWITCH_VERIFYCODE_RESPONSE:
				SPlayerSwitchZoneResponse response = (SPlayerSwitchZoneResponse) messageContent;
				if (response.getErrorCode() != ErrorCode.SUCCESS) {
					sendH5MsgToClient(bb, messageType);
				}
				CPlayerLoginZoneRequest cRequest = new CPlayerLoginZoneRequest();
				cRequest.setUserId(PlayerCommonInfoManager.getInstance()
						.getPlayerGuidBySession(message.getSessionID()));
				cRequest.setVerifyCode(response.getVerifyCode());
				// sRequest.setHalltype(()message.getMessageContent());
				H5GatewayDoSeat.sendLoginZoneMessage(cRequest, sessionId,
						(String) H5XSENetSessionPool.getSession(mChannel)
								.getParameters().get("targetModuleId"));
				break;
			case MessageTypes.S_PLAYER_LOGIN_ZONE_RESULT:
				SPlayerLoginZoneResult result = (SPlayerLoginZoneResult) messageContent;
				if (result.getResult() != ErrorCode.SUCCESS) {
					sendH5MsgToClient(bb, messageType);
				} else {
					String seatId = (String) H5XSENetSessionPool.getSession(mChannel).getParameters()
							.get("seatId");
					String moduleId = (String) H5XSENetSessionPool.getSession(mChannel).getParameters()
							.get("targetModuleId");
					String hallType = (String) H5XSENetSessionPool.getSession(mChannel).getParameters()
							.get("halltype");

					if (seatId != null) {
						SlotGameDoSeatVerifyRequest verifyRequest = new SlotGameDoSeatVerifyRequest();
						verifyRequest.setHalltype(hallType);
						verifyRequest.setSeatid(seatId);
						H5GatewayDoSeat.sendDoSeatVerify(verifyRequest, sessionId, moduleId);
					}
					// 百家樂的沒有seatID
					else if (ServerProperties.isTargetModule(moduleId, ModuleName.BACR_GAME)) {
						SlotGameDoSeatVerifyRequest verifyRequest = new SlotGameDoSeatVerifyRequest();
						verifyRequest.setHalltype(hallType);
						H5GatewayDoSeat.sendDoSeatVerify(verifyRequest, sessionId, moduleId);
					} else {
						sendH5MsgToClient(bb, messageType);
					}
				}
				break;
			case MessageTypes.S_BACR_DOORMAN_DOSEAT_BACK:
				SBacrDoSeatResponse bacrResponse = (SBacrDoSeatResponse) messageContent;
				if (bacrResponse.getErrorCode() == ErrorCode.SUCCESS) {
					request.setTargetZoneId(bacrResponse.getTargeModuleID());
					H5GatewayDoSeat.sendSwitchZoneMessage(request, sessionId);
					// 百家樂只須設定目標moduleID
					H5XSENetSessionPool.getSession(mChannel).getParameters()
							.put("targetModuleId", bacrResponse.getTargeModuleID());
				}
				else{
					sendH5MsgToClient(bb, messageType);					
				}

				break;
			default:
				JSONObject classToJsonExtra = new JSONObject();
				try {
					if (messageType == MessageTypes.S_SLOT_DOSEAT_VERIFY_BACK)
					{
						classToJsonExtra.put("moduleId", H5XSENetSessionPool.getSession(mChannel).getParameters().get("targetModuleId"));
						classToJsonExtra.put("seatId", H5XSENetSessionPool.getSession(mChannel).getParameters().get("seatId"));
					}
					else if(messageType == MessageTypes.S_BACR_DOSEATVERIFY) {
						classToJsonExtra.put("moduleId", H5XSENetSessionPool.getSession(mChannel).getParameters().get("targetModuleId"));
						// h5要額外提供伺服器當前時間
						classToJsonExtra.put("serverTime", TimeSys.currentTimeMills());
					}
					else if (messageType == MessageTypes.S_ACCOUNT_CHARACTER_KICK_BY_SERVER) {
						classToJsonExtra.put("kickByServer", "");
					}
				} catch (JSONException e) {
					// TODO: handle exception
					if (log.isErrorEnabled()) {
						log.error("h5 json parsed failed! - " + classToJsonExtra);
					}
				}
				sendH5MsgToClient(bb, messageType, classToJsonExtra);
				break;
			}
		}
	}

	public void sendH5MsgToClient(byte[] bb, int messageType, JSONObject... classToJsonExtras) {
		JSONObject classToJson = H5Coder.classToJson(CodecUtility
				.decodeByte(Arrays.copyOfRange(bb, 1, bb.length)));
		try {
			classToJson.put("messageType", messageType);
			if (classToJsonExtras != null && classToJsonExtras.length > 0) {
				for (JSONObject jsonObject : classToJsonExtras) {
					Iterator<String> iterator = jsonObject.keys();
					while (iterator.hasNext()) {
						String key = iterator.next();
						classToJson.put(key, jsonObject.get(key));
					}
				}
			}
			byte[] bbJson = classToJson.toString().getBytes("UTF-8");
			byte[] bbJsonLength = ByteBuffer.allocate(4)
						.putInt(bbJson.length).array();
			byte[] mergeBytes = BytebufferByte.mergeByteArrays(
					(bbJsonLength.length + bbJson.length), bbJsonLength,
					bbJson);
			int contentSplitTimes = mergeBytes.length / tcpPacketSize;
			int remainingContentLength = mergeBytes.length % tcpPacketSize;
			int initialIndex = 0;
			
			ByteBuffer buffer = null;
			try {
				buffer = ByteBuffer.wrap(mergeBytes, initialIndex, mergeBytes.length);
				ChannelFuture future = mChannel.write(buffer);
			} catch (Exception e) {
				log.error("mChannel.write error - " , e);
			}
			buffer.clear();
			
			/*for (int i = 0 ; i < contentSplitTimes ; i++) {
				ByteBuffer buffer = null;
				try {
					buffer = ByteBuffer.wrap(mergeBytes, initialIndex, tcpPacketSize);
					ChannelFuture future = mChannel.write(buffer);
					if (future == null || ! future.isSuccess()) {
						log.error("future.isSuccess false =" + future.getCause().getMessage());
					}
					initialIndex += tcpPacketSize;
				} catch (Exception e) {
					log.error("failed split multiple packets! - " + e.toString());
				}
				
				buffer.clear();
			}
			if (remainingContentLength != 0) {
				ByteBuffer buffer = null;
				try {
					buffer = ByteBuffer.wrap(mergeBytes, initialIndex, remainingContentLength);
					ChannelFuture future = mChannel.write(buffer);
					if (future == null || ! future.isSuccess()) {
						log.error("future.isSuccess false =" + future.getCause().getMessage());
					}
				} catch (Exception e) {
					// TODO: handle exception
					log.error("failed to split single packet! - " + e.toString());
				}
				buffer.clear();
			}*/
			H5XSEPacketCategoryMeter.getInstance().recordOutput(
					messageType, bbJson.length + 10);
		} catch (UnsupportedEncodingException e) {
			if (log.isErrorEnabled()) {
				log.error("unsupported encoding!");
			}
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			if (log.isErrorEnabled()) {
				log.error("failed to convert json to class!");
			}
		}
	}
	
	public void flush()
	{
		if( XSENetGlobal.mLibType == XSENetConstant.LIB_NETTYMODE )
		{
			//rowan:
			//mAutoFlusher.flush();

			//mIsSend.set(false);
		}
	}
	
	public H5XSENetFuture close()
	{
		//使用編譯最佳化
		if( XSENetGlobal.mLibType == XSENetConstant.LIB_CINDYMODE )
		{
			return new H5XSENetFuture(mSession.close());
		}
		else if( XSENetGlobal.mLibType == XSENetConstant.LIB_NETTYMODE )
		{
			return new H5XSENetFuture(mChannel.close());
		}
		
		return null;
	}
	
	/////////////////////////////////////////////////////////////////////////
	
//	private String mID;
//	
//	public void setID(String id)
//	{
//		mID = id;
//	}
//	public String getID()
//	{
//		return mID;
//	}

	private GUID mID;
	
	public void setID(GUID id)
	{
		mID = id;
	}
	public GUID getID()
	{
		return mID;
	}
	
	
//	//
//	private String mAccountID;
//	
//	public void setAccountID(String id)
//	{
//		mAccountID = id;
//	}
//	public String getAccountID()
//	{
//		return mAccountID;
//	}
	

	/////////////////////////////////////////////////////////////////////////
	
	//private static final FlushTimer mFlusher = new FlushTimer();
	//private final AtomicBoolean mIsSend = new AtomicBoolean(false);
	
	/*
	private void forward(long time)
	{
		if( mIsSend.compareAndSet(false, true) )
		{
			mFlusher.add(time, this);
		}
	}
	*/
	
	public static void initialize()
	{
		//為了保持數據發送的順序, 使用一個Thread 
		if( XSENetGlobal.mLibType == XSENetConstant.LIB_NETTYMODE )
		{
			/*
			Thread pThread = new Thread(mFlusher);
			pThread.setDaemon(true);
			pThread.start();
			*/
		}
	}

	public HashMap<String, Object> getParameters() {
		return parameters;
	}

	public void setParameters(HashMap<String, Object> parameters) {
		this.parameters = parameters;
	}
}

////////////////////////////////////////////////////////////////////////

class _FlushTimer implements Runnable
{
	private TreeMap<Long, ArrayList<H5XSENetSession>> mFlushPool = new TreeMap<Long, ArrayList<H5XSENetSession>>();
	
	public void add(long time, H5XSENetSession flusher)
	{

		 synchronized(mFlushPool)
		 {
			 ArrayList<H5XSENetSession> list = mFlushPool.get(time);
			 if( list == null )
			 {
				 list = new ArrayList<H5XSENetSession>();
				 mFlushPool.put(time, list);
			 }
			 list.add(flusher);
		 }
	}
	
	@Override
	public void run()
	{
		 long uNOW;
			
		 LinkedList<H5XSENetSession> flushList = new LinkedList<H5XSENetSession>();
		 
		 while( true )
		 {
			try
			{
				uNOW = System.currentTimeMillis();
				
				synchronized(mFlushPool)
				{
					if( mFlushPool.size() > 0 )
					{
						Iterator<Entry<Long, ArrayList<H5XSENetSession>>> itr = mFlushPool.entrySet().iterator();
						while( itr.hasNext() )
					    {
							Entry<Long, ArrayList<H5XSENetSession>> pairs = itr.next();
						    	
							long nCastTime = pairs.getKey();
							if( uNOW >= nCastTime )
							{
								flushList.addAll(pairs.getValue());
								itr.remove();
							}
							else
								break;
						}
					}
				}
				
				//
				if( flushList.size() > 0 )
				{
					for(H5XSENetSession flusher : flushList)
					{
						flusher.flush();
					}
						
					flushList.clear();
				}
					
				Thread.sleep(1);
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}
		 }
	}
}